package org.restorator.di;

import org.restorator.exception.ServiceLocatorException;
import org.restorator.utill.GeneralUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

/**
 * @author Snisar Bogdan.
 *
 */
class InitialContext {

    public static final String GET_SERVICE = "/ref";
    public static final String KEY_ATTR = "key";
    public static final String CONSTRUCTOR_TEG = "constructor-args";
    public static final String ARG_REF = "arg-ref";
    public static final String CLASS_ATTR = "class";
    public static final String TYPE_ATTR = "type";


    private ServiceBuilder builder;

    private static Logger log = LoggerFactory.getLogger(GeneralUtil.getClassName());


    InitialContext(ServiceBuilder builder) {
        this.builder = builder;
    }
    private Document doc;
    XPath xPath;

    public void initParsing() {
        DocumentBuilder dBuilder = null;
        try {
            InputStream in = this.getClass().getClassLoader().getResourceAsStream("context.xml");
            DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
            dBuilder = dbFactory.newDocumentBuilder();
            doc = dBuilder.parse(in);
            xPath =  XPathFactory.newInstance().newXPath();
        } catch (ParserConfigurationException | SAXException | IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Parse context.xml and find all beans
     */
    public void parse() {
        initParsing();
        String key = null;
        try {
            NodeList nodeList = (NodeList) xPath.compile("/context/ref").evaluate(doc, XPathConstants.NODESET);
            for(int i = 0; i < nodeList.getLength(); i++) {
                Element e = (Element) nodeList.item(i);
                key = e.getAttribute(KEY_ATTR);
                String className = e.getAttribute(CLASS_ATTR);
                String single = e.getAttribute("single");
                log.debug("append ref key [{}], class [{}], single [{}]", key, className, single);
                builder.appendService(key, className, Boolean.valueOf(single));
            }

            for (int i = 0; i < nodeList.getLength(); i++) {
                Element e = (Element) nodeList.item(i);
                addConstructor(e);

            }
        } catch (XPathExpressionException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            throw  new ServiceLocatorException("no suitable constructor for ref=" + key);
        }
    }

    /**
     * Find construct teg for all.
     *
     * @param element teg represent bean description.
     */
    private void addConstructor(Element element) {
        NodeList constructorTeg = element.getElementsByTagName(CONSTRUCTOR_TEG);
        String key = element.getAttribute(KEY_ATTR);
        log.debug("Try add constructor to ref with key={}", key);
        Element e = (Element) constructorTeg.item(0); //constructor teg should be one
        if(e != null) {
            ArrayList<Object> args = new ArrayList<>(5);
            NodeList cList = e.getChildNodes();
            for (int i = 0; i < cList.getLength(); i++) {
                Node item = cList.item(i);
                if(item.getNodeName().equals(ARG_REF)) {
                    String constructorArgText = item.getTextContent();
                    if(constructorArgText == null)
                        throw  new ServiceLocatorException("empty depended <arg-ref> teg in " + element.getTagName());
                    args.add(appendRefToTheArguments(constructorArgText));
                } else if(item.getNodeName().equals("arg-value")) {
                    Element arg = (Element) item;
                    args.add(typeResolve(arg));
                }
            }
            try {
                builder.appendConstructor(key, null, args.toArray());
            } catch (NoSuchMethodException e1) {
                throw new ServiceLocatorException("No suitable constructor method in ref with key=" + key);
            }
        }
    }

    /**
     *
     *
     * @return
     *
     */
    private Object typeResolve(Element argTeg) {
        try {
        String value = argTeg.getTextContent();
        String wrapperClass = argTeg.getAttribute(TYPE_ATTR);
            Class<?> aClass = Class.forName(wrapperClass);
            if(aClass.isPrimitive()) {
                Method method = aClass.getMethod("valueOf", String.class);
                return method.invoke(null, value);
            } else
                throw new ServiceLocatorException("Illegal type for teg attribute <type>, it must be primitive wrapper");
        } catch (ClassNotFoundException | InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
            throw new ServiceLocatorException("Illegal parameter type in in teg <arg-value> in teg " + argTeg.getTagName());
        }
    }

    /**
     * Find ref bay key.
     *
     * @param refKey ref's key
     */
    private Ref appendRefToTheArguments(String refKey) {
        Ref ref = builder.getRefFromKey(refKey);
        if(ref == null)
            throw  new ServiceLocatorException("try find ref by key=" + refKey + " that hadn't been added to builder");
        return ref;
    }

//    private void check


}
